package crp.services.api.graphql;

import com.qwlabs.graphql.relay.Connection;
import com.qwlabs.graphql.relay.Relays;
import com.qwlabs.lang.S2;
import com.qwlabs.panache.Ranged;
import crp.core.course.app.CourseService;
import crp.core.course.domain.models.CourseEntity;
import crp.core.course.domain.models.CourseSourceType;
import crp.core.course.domain.models.ExistedCourseSource;
import crp.core.course.domain.services.CourseSearcher;
import crp.core.security.Callers;
import crp.core.user.domain.models.UserEntity;
import crp.services.api.graphql.models.CoursePayload;
import crp.services.api.graphql.models.CreateCourseInput;
import io.quarkus.panache.common.Range;
import io.quarkus.security.identity.SecurityIdentity;
import org.eclipse.microprofile.graphql.DefaultValue;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Mutation;
import org.eclipse.microprofile.graphql.Query;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Optional;

import static crp.core.security.domain.models.Permissions.CORE_COURSE_EDIT;

@GraphQLApi
@RequestScoped
@Transactional
public class CourseGraphQLApi {
    private final CourseService courseService;
    private final SecurityIdentity securityIdentity;

    @Inject
    public CourseGraphQLApi(CourseService courseService,
                            SecurityIdentity securityIdentity) {
        this.courseService = courseService;
        this.securityIdentity = securityIdentity;
    }

    @Query
    @PermitAll
    @NotNull
    public Connection<CoursePayload> courses(
            Optional<String> keyword,
            Optional<String> academicTermId,
            Optional<String> openDepartmentId,
            Optional<String> sourceType,
            Optional<String> existedCourseSource,
            @DefaultValue("0") String after,
            @DefaultValue("10") Integer first) {
        return Relays.of(courseService.searcher()
                        .by(keyword.orElse(null),
                                academicTermId.orElse(null),
                                openDepartmentId.orElse(null),
                                sourceType.map(CourseSourceType::valueOf).orElse(null),
                                existedCourseSource.map(ExistedCourseSource::valueOf).orElse(null),
                                Relays.of(after, first)),
                CoursePayload::of);
    }

    @Query
    @PermitAll
    @NotNull
    public Connection<CoursePayload> publicCourses(
            Optional<String> keyword,
            @DefaultValue("0") String after,
            @DefaultValue("10") Integer first) {
        return Relays.of(courseService.searcher()
                        .publicBy(keyword.orElse(null), Relays.of(after, first)),
                CoursePayload::of);
    }

    @Query
    @PermitAll
    @NotNull
    public Connection<CoursePayload> studentCourses(
            Optional<String> academicTermId,
            Optional<String> studentId,
            @DefaultValue("0") String after,
            @DefaultValue("10") Integer first) {
        return Relays.of(courseService.searcher()
                        .studentBy(studentId.orElse(null), academicTermId.orElse(null),
                                Relays.of(after, first)),
                CoursePayload::of);
    }

    @Query
    @PermitAll
    @NotNull
    public Connection<CoursePayload> facultyCourses(
            Optional<String> academicTermId,
            @DefaultValue("0") String after,
            @DefaultValue("10") Integer first) {
        return Relays.of(courseService.searcher()
                        .facultyBy(academicTermId.orElse(null),
                                Relays.of(after, first)),
                CoursePayload::of);
    }

    @Query
    @PermitAll
    @NotNull
    public Connection<CoursePayload> searchCourses(
            Optional<String> keyword,
            @DefaultValue("0") String after,
            @DefaultValue("10") Integer first) {
        Optional<UserEntity> mayUser = Optional.ofNullable(Callers.user());
        CourseSearcher searcher = courseService.searcher();
        Range range = Relays.of(after, first);
        if (mayUser.isEmpty()) {
            Ranged<CourseEntity> courses = searcher.publicBy(keyword.orElse(null), range);
            return Relays.of(courses, CoursePayload::of);
        }

        UserEntity userEntity = mayUser.get();
        if (S2.isNotBlank(userEntity.getFacultyId())) {
            Ranged<CourseEntity> courses = searcher.facultyPublicBy(keyword.orElse(null), range);
            return Relays.of(courses, CoursePayload::of);
        }
        if (S2.isNotBlank(userEntity.getStudentId())) {
            Ranged<CourseEntity> courses = searcher.studentPublicBy(userEntity.getStudentId(), keyword.orElse(null),
                    range);
            return Relays.of(courses, CoursePayload::of);
        }

        Ranged<CourseEntity> courses = searcher.by(keyword.orElse(null), null, null, null, null, range);
        return Relays.of(courses, CoursePayload::of);
    }

    @Mutation
    @NotNull
    @RolesAllowed(CORE_COURSE_EDIT)
    public CoursePayload createCourse(@NotNull @Valid CreateCourseInput input) {
        return CoursePayload.of(courseService.editor()
                .upsert(input.getCode(), input.getName(), input.getOpenDepartmentId()));
    }

    @Query
    @PermitAll
    public CoursePayload course(@NotBlank String id) {
        return courseService.searcher().byId(id)
                .map(CoursePayload::of)
                .orElse(null);
    }

    @Query
    @PermitAll
    public CoursePayload myCourse(@NotBlank String id) {
        return courseService.searcher().byId(id)
                .map(CoursePayload::of)
                .orElse(null);
    }
}